using System;
using System.Collections.Generic;
using System.Text;

namespace Engine
{
    public class MathExpression
    {
        public MathExpression(string definition) 
        {
            pos = 0;
            parse(definition);
        }

        // Ham dung de tinh toan gia tri bieu thuc 
        public void CaculateMath(string definition) 
        {
            pos = 0;
            parse(definition);
        }

        private void error(String message)
        {  // called when an error occurs during parsing
            throw new Exception("Parse error:  " + message + "  (Position in data = " + pos + ".)");
        }

        private int computeStackUsage()
        {  
            int s = 0;   // stack size after each operation
            int max = 0; // maximum stack size seen
            for (int i = 0; i < codeSize; i++)
            {
                if (code[i] >= 0 || code[i] == VARIABLE_X
                    || code[i] == VARIABLE_Y || code[i] == VARIABLE_Z
                    || code[i] == E || code[i] == PI
                   )
                {
                    s++;
                    if (s > max)
                        max = s;
                }
                else if (code[i] >= POWER)
                    s--;
            }
            return max;
        }

        private void parse(String definition)
        {  
            if (definition == null || definition.Trim().Equals(""))
                error("No data provided to Expr constructor");
            this.definition = definition;
            code = new int[definition.Length];
            constants = new double[definition.Length];
            parseExpression();
            skip();
            if (next() != 0)
                error("Extra data found after the end of the expression.");
            int stackSize = computeStackUsage();
            stack = new double[stackSize];
            int[] c = new int[codeSize];
            //System.arraycopy(code, 0, c, 0, codeSize);
            Array.Copy(code, c, codeSize);
            code = c;
            double[] A = new double[constantCt];
            Array.Copy(constants, A, constantCt);
            constants = A;
        }

        private char next()
        {  // return next char in data or 0 if data is all used up
            if (pos >= definition.Length)
                return (char)0;
            else
                return definition[pos];
        }

        private void skip()
        {  // skip over white space in data
            while (System.Char.IsWhiteSpace(next()))
                pos++;
        }

        // remaining routines do a standard recursive parse of the expression

        private void parseExpression()
        {
            bool neg = false;
            skip();
            if (next() == '+' || next() == '-')
            {
                neg = (next() == '-');
                pos++;
                skip();
            }
            parseTerm();
            if (neg)
                code[codeSize++] = UNARYMINUS;
            skip();
            while (next() == '+' || next() == '-')
            {
                char op = next();
                pos++;
                parseTerm();
                if (op == '+')
                    code[codeSize++] = PLUS;
                else
                    code[codeSize++] = MINUS;
                skip();
            }
        }

        private void parseTerm()
        {
            parseFactor();
            skip();
            while (next() == '*' || next() == '/')
            {
                char op = next();
                pos++;
                parseFactor();
                if (op == '*')
                    code[codeSize++] = TIMES;
                else
                    code[codeSize++] = DIVIDE;
                skip();
            }
        }

        private void parseFactor()
        {
            parsePrimary();
            skip();
            while (next() == '^')
            {
                pos++;
                parsePrimary();
                code[codeSize++] = POWER;
                skip();
            }
        }

        private void parsePrimary()
        {
            skip();
            char ch = next();
            /*if (ch == 'x' || ch == 'X')
            {
                pos++;
                code[codeSize++] = VARIABLE_X;
            }
            else if (ch == 'y' || ch == 'Y')
            {
                pos++;
                code[codeSize++] = VARIABLE_Y;
            }
            else if (ch == 'z' || ch == 'Z')
            {
                pos++;
                code[codeSize++] = VARIABLE_Z;
            }
            if (ch == 'P' || ch == 'p')
            {
                pos++;
                code[codeSize++] = PI;
            }

            else if (ch == 'N' || ch == 'n')
            {
                pos++;
                code[codeSize++] = E;
            }*/
            if (Char.IsLetter(ch))
                parseWord();
            else if (Char.IsDigit(ch) || ch == '.')
                parseNumber();
            else if (ch == '(')
            {
                pos++;
                parseExpression();
                skip();
                if (next() != ')')
                    error("Exprected a right parenthesis.");
                pos++;
            }
            else if (ch == ')')
                error("Unmatched right parenthesis.");
            else if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '^')
                error("Operator '" + ch + "' found in an unexpected position.");
            else if (ch == 0)
                error("Unexpected end of data in the middle of an expression.");
            else
                error("Illegal character '" + ch + "' found in data.");
        }

        private void parseWord()
        {
            String w = "";
            while (Char.IsLetterOrDigit(next()))
            {
                w += next();
                pos++;
            }
            w = w.ToLower();
            for (int i = 0; i < functionNames.Length; i++)
            {
                if (w.Equals(functionNames[i]))
                {
                    skip();
                    if (next() != '(')
                        error("Function name '" + w + "' must be followed by its parameter in parentheses.");
                    pos++;
                    parseExpression();
                    skip();
                    if (next() != ')')
                        error("Missing right parenthesis after parameter of function '" + w + "'.");
                    pos++;
                    code[codeSize++] = (SIN - i);
                    return;
                }
            }
            error("Unknown word '" + w + "' found in data.");
        }

        private void parseNumber()
        {
            String w = "";
            while (Char.IsDigit(next()))
            {
                w += next();
                pos++;
            }
            if (next() == '.')
            {
                w += next();
                pos++;
                while (Char.IsDigit(next()))
                {
                    w += next();
                    pos++;
                }
            }
            if (w.Equals("."))
                error("Illegal number found, consisting of decimal point only.");
            /*if (next() == 'E' || next() == 'e')
            {
                w += next();
                pos++;
                if (next() == '+' || next() == '-')
                {
                    w += next();
                    pos++;
                }
                if (!Char.IsDigit(next()))
                    error("Illegal number found, with no digits in its exponent.");
                while (Char.IsDigit(next()))
                {
                    w += next();
                    pos++;
                }
            }*/
            double d = Double.NaN;
            try
            {
                d = Double.Parse(w);
            }
            catch (Exception e)
            {
            }
            if (Double.IsNaN(d))
                error("Illegal number '" + w + "' found in data.");
            code[codeSize++] = constantCt;
            constants[constantCt++] = d;
        }

        // Ham tinh gia tri mot bien
        public double value(double x) 
        {
            return eval(x,0,0);
        }

        public double value(double x, double y) 
        {
            return eval(x, y, 0);
        }

        public double value(double x, double y, double z)
        {
            return eval(x, y, z);
        }

        private double eval(double variableX, double variableY, double variableZ) 
        {
            try
            {
                int top = 0;
                for (int i = 0; i < codeSize; i++)
                {
                    if (code[i] >= 0)
                        stack[top++] = constants[code[i]];
                    else if (code[i] >= POWER)
                    {
                        double y = stack[--top];
                        double x = stack[--top];
                        double ans = Double.NaN;
                        switch (code[i])
                        {
                            case PLUS: ans = x + y; break;
                            case MINUS: ans = x - y; break;
                            case TIMES: ans = x * y; break;
                            case DIVIDE: ans = x / y; break;
                            case POWER: ans = Math.Pow(x,y); break;
                        }
                        if (Double.IsNaN(ans))
                            return ans;
                        stack[top++] = ans;
                    }
                    /*else if (code[i] == VARIABLE_X)
                    {
                        stack[top++] = variableX;

                    }
                    else if (code[i] == VARIABLE_Y)
                    {
                        stack[top++] = variableY;

                    }
                    else if (code[i] == VARIABLE_Z)
                    {
                        stack[top++] = variableZ;

                    }
                    else if (code[i] == E)
                    {
                        stack[top++] = Math.E;
                    }
                    else if (code[i] == PI)
                    {
                        stack[top++] = Math.PI;
                    }*/
                    else
                    {
                        double x = stack[--top];
                        double ans = Double.NaN;
                        switch (code[i])
                        {
                            case SIN: ans = Math.Sin(x); break;
                            case COS: ans = Math.Cos(x); break;
                            case TAN: ans = Math.Tan(x); break;
                            case COT: ans = Math.Cos(x) / Math.Sin(x); break;
                            case SEC: ans = 1.0 / Math.Cos(x); break;
                            case CSC: ans = 1.0 / Math.Sin(x); break;
                            case ARCSIN: if (Math.Abs(x) <= 1.0) ans = Math.Asin(x); break;
                            case ARCCOS: if (Math.Abs(x) <= 1.0) ans = Math.Acos(x); break;
                            case ARCTAN: ans = Math.Atan(x); break;
                            case EXP: ans = Math.Exp(x); break;
                            case LN: if (x > 0.0) ans = Math.Log(x); break;
                            case LOG2: if (x > 0.0) ans = Math.Log(x) / Math.Log(2); break;
                            case LOG10: if (x > 0.0) ans = Math.Log(x) / Math.Log(10); break;
                            case ABS: ans = Math.Abs(x); break;
                            case SQRT: if (x >= 0.0) ans = Math.Sqrt(x); break;
                            case UNARYMINUS: ans = -x; break;
                            case ROUND: ans = Math.Round(x); break;
                        }
                        if (Double.IsNaN(ans))
                            return ans;
                        stack[top++] = ans;

                    }
                }
            }
            catch (Exception e)
            {
                return Double.NaN;
            }
            if (Double.IsInfinity(stack[0]))
                return Double.NaN;
            else
                return stack[0];            
   }
   private int pos = 0, constantCt = 0, codeSize = 0;  // data for use during parsing

        public string getDefinition() 
        {
            return definition;
        }
        
        private string definition;

        private int[] code;

        private double[] stack;
        private double[] constants;
        private const int
        PLUS = -1, MINUS = -2, TIMES = -3, DIVIDE = -4, POWER = -5,
        SIN = -6, COS = -7, TAN = -8, COT = -9, SEC = -10,
        CSC = -11, ARCSIN = -12, ARCCOS = -13, ARCTAN = -14, EXP = -15,
        LN = -16, LOG10 = -17, LOG2 = -18, ABS = -19, SQRT = -20,
        UNARYMINUS = -22, VARIABLE_X = -23, VARIABLE_Y = -24, VARIABLE_Z = -25, E = -26, PI = -27, ROUND = -21;

        private static string[] functionNames =  // names of standard functions, used during parsing
	    {   "sin", "cos", "tan", "cot", "sec",
			"csc", "arcsin", "arccos", "arctan", "exp",
			"ln", "log10", "log2", "abs", "sqrt" , "round"
        };
  }
}
